/* Programa que recebe uma matriz fornecida pelo usuario, imprime a matriz original, calcula e imprime a transposta e
 * calcula e imprime a multiplicacao da transposta pela original.
 * Autor: Jose Geraldo de Carvalho Pereira	RA: 482536 */ 
#include <stdio.h>
#include <stdlib.h>

typedef struct matriz Matriz;
struct matriz {
	int linhas;
	int colunas;
	double **elementos;
};

int leMatriz(Matriz *matriz){
	// Funcao que le os dados fornecidos pelo usuario para uma matriz

	int i, j;
	printf("Numero de linhas: ");
	scanf("%d", &matriz->linhas);
	if (matriz->linhas < 1){
		printf("Erro: numero de linhas deve ser MAIOR que 0\n");
		return -1;
	}
	
	printf("Numero de colunas: ");
	scanf("%d", &matriz->colunas);
	if (matriz->colunas < 1){
		printf("Erro: numero de colunas deve ser MAIOR que 0\n");
		return -1;
	}

	//aloca dinamicamente um vetor para armazenas os elementos da matriz
	matriz->elementos = malloc(matriz->linhas * sizeof(double *));
	if (matriz->elementos == NULL) {
		printf("Erro de alocacao para a leitura da matriz (linhas)\n");
		return -1;
	}
	matriz->elementos[0] = malloc(matriz->linhas * matriz->colunas * sizeof(double));
	if (matriz->elementos[0] == NULL) {
		printf("Erro de alocacao para a leitura da matriz (colunas)\n");
		return -1;
	}
	//cria um indice para facilitar a manipulacao da matriz
	for (i=1; i < matriz->linhas; i++){
		matriz->elementos[i] = &matriz->elementos[0][i * matriz->colunas];
	}

	//le os elementos fornecidos pelo usuario
	for (i=0; i < matriz->linhas; i++){
		for (j=0; j < matriz->colunas; j++){
			printf("Elemento (%d, %d): ", i+1, j+1);
		       	scanf("%lf", &matriz->elementos[i][j]);	
		}
	}
	printf("\n");
	return 0;
}

void imprimeMatriz(Matriz *matriz){
	// Funcao que imprime o numero de linhas e colunas e tambem os elementos da matriz
	int i, j;
	printf("Matriz %d x %d\n", matriz->linhas, matriz->colunas);
	for (i=0; i < matriz->linhas; i++){
		for (j=0; j < matriz->colunas; j++){
			printf("  %5.3f", matriz->elementos[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

Matriz calcTransposta(Matriz *matriz){
	//Funcao que recebe uma matriz e retorna a matriz transposta dela
	Matriz trans;
	int i, j;

	trans.linhas = matriz->colunas;
	trans.colunas = matriz->linhas;

	//aloca dinamicamente vetores para armazenar a matriz
	trans.elementos = malloc(trans.linhas * sizeof(double *));
	if (trans.elementos == NULL) {
		printf("Erro de alocacao para o calculo da matriz transposta(linhas)\n");
		trans.linhas = 0; 	//muda o numero de linhas e colunas para zero para
		trans.colunas = 0;	//indicar que houve um erro na alocacao
		return trans;
	}
	trans.elementos[0] = malloc(trans.linhas * trans.colunas * sizeof(double));
	if (trans.elementos[0] == NULL) {
		printf("Erro de alocacao para o calculo da matriz transposta (colunas)\n");
		trans.linhas = 0;	//muda o numero de linhas e colunas para zero para
		trans.colunas = 0;	//indicar que houve um erro de alocacao
		return trans;
	}

	//cria um indice no vetor para facilitar a manipulacao da matriz
	for (i=1; i < trans.linhas; i++){
		trans.elementos[i] = &trans.elementos[0][i * trans.colunas];
	}
	
	//atribui os elementos da matriz transposta de acordo com a matriz fornecida
	for (i=0; i < trans.linhas; i++) {
		for (j=0; j < trans.colunas; j++) {
			trans.elementos[i][j] = matriz->elementos[j][i];
		}
	}
	return trans;
}

Matriz multMatrizes(Matriz *mat1, Matriz *mat2){
	// Funcao que recebe duas matrizes e retorna a matriz resultante 
	// da multiplicacao delas (mat1 x mat2)
	Matriz result;
	int i,j, k, l;

	
	// testa se a multiplicacao das matrizes pode ser feita
	if (mat1->colunas != mat2->linhas){
		printf("Nao e possivel multiplicar as matrizes");
		result.linhas = 0;	//muda o numero de linhas e colunas para 0 para indicar que
		result.colunas = 0;	//a multiplicacao nao pode ser feita
		return result;
	}		
	result.linhas = mat1->linhas;
	result.colunas = mat2->colunas;

	//aloca memoria dinamicamente para armazenar o resultado da multiplicacao de matrizes 
	result.elementos = malloc(result.linhas * sizeof(double *));
	if (result.elementos == NULL) {
		printf("Erro de alocacao de memoria na multiplicacao de matrizes (linhas)\n");
		result.linhas = 0;	//muda o numero de linhas e colunas para 0 para 
		result.colunas = 0;	//indicar que houve erro na alocacao 
		return result;
	}
	result.elementos[0] = malloc(result.linhas * result.colunas * sizeof(double));
	if (result.elementos[0] == NULL) {
		printf("Erro de alocacao de memoria na multiplicacao de matrizes (colunas)\n");
		result.linhas = 0;	//muda o numero de linhas e colunas para 0 para
		result.colunas = 0;	//indicar que houve erro na alocacao 
		return result;
	}

	//cria um indice no vetor para facilitar a manipulacao da matriz
	for (i=1; i < result.linhas; i++){
		result.elementos[i] = &result.elementos[0][i * result.colunas];
	}

	//calcula a matriz resultante da multiplicacao das matrizes 1 e 2
	for (i=0; i < result.linhas; i++){
		for (j=0; j < result.colunas; j++){
			result.elementos[i][j] = 0;
			for (k = 0; k < mat1->colunas; k++){
				result.elementos[i][j] += mat1->elementos[i][k] * mat2->elementos[k][j];
			}
		}
	}
	return result;
}

void liberaMatriz(Matriz *matriz){
	// libera a memoria alocada dinamicamente para a matriz
	free(matriz->elementos[0]);
	free(matriz->elementos);
}

int main(){
	Matriz matriz, transposta, multi;

	while (leMatriz(&matriz) != 0)	//repete a leitura da matriz enquanto o usuario nao fornecer dados validos
		;

	printf("Matriz original\n");
	imprimeMatriz(&matriz);

	printf("Matriz transposta\n");
	transposta = calcTransposta(&matriz);
	imprimeMatriz(&transposta);
	
	printf("Multiplicacao da transposta pela matriz original\n"); 
	multi = multMatrizes(&transposta, &matriz);
	imprimeMatriz(&multi);

	liberaMatriz(&matriz); 
	liberaMatriz(&transposta);
	liberaMatriz(&multi);
	
	return 0;
}
	

